package com.cloudy.s3.request;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import com.cloudy.exception.AWSAccessException;
import com.cloudy.impl.AWSRequest;
import com.cloudy.impl.S3Object;
import com.cloudy.impl.S3ServiceCaller;
import com.cloudy.impl.SuccessHandler;
import com.cloudy.s3.response.AWSResponse;
import com.cloudy.security.S3RequestSigner;
import com.cloudy.utility.CloudyUtility;

public abstract class S3Request implements AWSRequest{

	private static final String EMPTY_STRING = "";
	private static final String COLON = ":";
	private static final String X_AMZ = "x-amz";
	private static final char WHITE_SPACE = ' ';
	private static final char NEW_LINE = '\n';
	private static final String COMMA = ",";
	final static String S3_TIMESTAMP_PATTERN = "EEE, dd MMM yyyy HH:mm:ss ";
	protected String signature;
	protected String timeStamp;
	protected SuccessHandler successHandler;
	protected Map<String, String> attributes = new HashMap<String, String>();
	private Map<String, List<String>> headers = new HashMap<String, List<String>>();
	protected S3RequestSigner signer;
	protected String bucket;
	protected String subResource = EMPTY_STRING;
	protected String resource = EMPTY_STRING;
	protected Object body;
	private S3Object s3Object;
	private String requestMethod;
	public static S3RequestSigner requestSigner = new S3RequestSigner();
	public static S3ServiceCaller serviceCaller = new S3ServiceCaller();

	public S3Request(String bucket, String requestMethod) {
		this.requestMethod = requestMethod;
		this.bucket = bucket;
		timeStamp = createDateInAWSFormat(new Date());
		signer = new S3RequestSigner();

	}
	public String getRequestMethod(){
		return requestMethod;
	}
	
	public Map<String, String> getAttributes() {
		return attributes;
	}

	public void putSignature(String value) {
		this.signature = value;
		
	}

	protected String createDateInAWSFormat(Date date) {
	    SimpleDateFormat df = new SimpleDateFormat(S3_TIMESTAMP_PATTERN, Locale.US);
	    df.setTimeZone(TimeZone.getTimeZone("GMT"));
	    return df.format(date) + "GMT";
	}
	
	public String getTimeStamp(){
		return timeStamp;
	}
	public String getSignature(){
		return signature;
	}

	protected <T> AWSResponse<T> executeInner() throws AWSAccessException {
		requestSigner.sign(this);
		return serviceCaller.callService(this);
	}

	public void setSuccessHandler(SuccessHandler successHandler) {
		this.successHandler = successHandler;
	}

	public SuccessHandler getSuccessHandler() {
		return successHandler;
	}

	public String getBucket() {
		return bucket;
	}

	public String getQueryString() {
		String dash = EMPTY_STRING;
		String resource =  this.resource;
		String queryString = CloudyUtility.getQueryString(attributes);
		
		if(queryString!=null && !queryString.equals(EMPTY_STRING) && resource !=null && !resource.equals(EMPTY_STRING))
			dash = "/";

		return resource + dash + queryString;
	}
	public Object getBody(){
		return body;
	}
	public String getContentType(){
		if(s3Object!=null){
			return s3Object.getContentType();
		}
		else
			return EMPTY_STRING;
	}
	
	public String getSubResource(){
		if(subResource!=null && !subResource.equals(EMPTY_STRING))
			return "?"+subResource;
		else return subResource;
	}
	public String getResource(){
		return resource;
	}

	public void setS3Object(S3Object obj) {
		this.s3Object = obj;
	}

	public S3Object getS3Object() {
		return s3Object;
	}

	public String getContentMD5() {
		if(s3Object!=null){
			return s3Object.getContentMD5();
		}
		else
			return EMPTY_STRING;
	}
	
	public void addHeader(String key, String headerElement) {
		if(headers.get(key)==null){
			headers.put(key, new ArrayList<String>());
		}
		headers.get(key).add(headerElement);
	}
	
	public Map<String, List<String>> getHeaders(){
		return headers;		
	}

	public String getCanonicalizedAmzHeaders() {
		List<String> amzHeaders = getAmzHeaders();
		List<String> canonicalizedAmzHeaders = getUnsortedCanonicalizedAmzHeaders(amzHeaders);
		Collections.sort(canonicalizedAmzHeaders);
		StringBuilder canonicalizedAmzHeader = new StringBuilder();
		for(String entry: canonicalizedAmzHeaders){
			canonicalizedAmzHeader.append(entry).append(NEW_LINE);
		}
		return canonicalizedAmzHeader.toString();
//		return "";
	}

	private List<String> getUnsortedCanonicalizedAmzHeaders(
			List<String> amzHeaders) {
		List<String> canonicalizedAmzHeaders = new ArrayList<String>();
		for(String headerName: amzHeaders ){
			List<String> values = headers.get(headerName);
			StringBuilder stringBuilder = new StringBuilder(headerName.toLowerCase().trim()).append(COLON);
			for(String value: values){
				stringBuilder.append(value.trim()).append(COMMA);
			}
			String entry = stringBuilder.toString();
			
			while(entry.endsWith(COMMA)){
				entry = entry.substring(0, entry.length()-1);
			}
			entry.replace(NEW_LINE, WHITE_SPACE);
			canonicalizedAmzHeaders.add(entry);
		}
		return canonicalizedAmzHeaders;
	}

	private List<String> getAmzHeaders() {
		List<String> amzHeaders = new ArrayList<String>();
		for(String headerName: headers.keySet()){
			String lowerCase = headerName.toLowerCase();
			if(lowerCase.startsWith(X_AMZ))
				amzHeaders.add(headerName);
		}
		return amzHeaders;
	}
}
